package com.github.cyf.databaseLock.impl;

import com.github.cyf.databaseLock.AbstractSimpleLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.transaction.annotation.Transactional;

import java.time.Duration;

/**
 * @author chenyifan
 * @create 2024-07-11 15:56
 */
@Slf4j
public class MysqlLock extends AbstractSimpleLock {

    /**
     * 获取锁
     * 原始 sql 需要配合 DuplicateKeyException使用，不优雅：INSERT INTO distribute_lock (lock_key, token, expire, thread_id) VALUES (?, ?, ?, ?);
     * 使用 IGNORE 关键字意味着如果插入的数据导致任何约束冲突（例如 lock_key 已经存在，而它是唯一索引列），MySQL 不会抛出错误，而是忽略这个冲突，就好像插入尝试从未发生过一样。
     */
    private static final String ACQUIRE_LOCK_SQL = "INSERT IGNORE INTO distribute_lock(lock_key, token, thread_id, expire) VALUES (?, ?, ?, ?);";
    /**
     * 释放锁
     */
    private static final String RELEASE_LOCK_SQL = "DELETE FROM distribute_lock WHERE lock_key = ? AND token = ?;";
    /**
     * 刷新锁的过期时间
     */
    private static final String REFRESH_LOCK_SQL = "UPDATE distribute_lock SET expire = ? WHERE lock_key = ? AND token = ?;";
    /**
     * 删除过期的锁
     */
    private static final String DELETE_EXPIRE_LOCK_SQL = "DELETE FROM distribute_lock WHERE expire < ?;";

    private final JdbcTemplate jdbcTemplate;

    public MysqlLock(ThreadPoolTaskScheduler taskScheduler, JdbcTemplate jdbcTemplate) {
        super(taskScheduler);
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    @Transactional
    public String doAcquire(String key, String token) {
        long now = System.currentTimeMillis();
        // 删除由于一些异常原因导致没有删除的锁
        jdbcTemplate.update(DELETE_EXPIRE_LOCK_SQL, now);
        // 获取锁
        long expire = System.currentTimeMillis() + Duration.ofSeconds(LOCK_HOLD_TIME).toMillis();
        int update = jdbcTemplate.update(ACQUIRE_LOCK_SQL, key, token, Thread.currentThread().getName(), expire);
        log.info("获取锁成功，key:{}, token:{}", key, token);
        return update == 1 ? token : null;
    }

    @Override
    public boolean doRelease(String key, String token) {
        int update = jdbcTemplate.update(RELEASE_LOCK_SQL, key, token);
        log.info("释放锁成功，key:{}, token:{}", key, token);
        return update == 1;
    }

    @Override
    public boolean refreshLock(String key, String token) {
        long expire = System.currentTimeMillis() + Duration.ofSeconds(LOCK_HOLD_TIME).toMillis();
        int update = jdbcTemplate.update(REFRESH_LOCK_SQL, expire, key, token);
        log.info("刷新锁过期时间成功，key: {}, update: {}", key, update);
        return update == 1;
    }

}